/*
 * xpfsm.c
 *
 *  Created on: 2/6/2010
 *      Author: apollo
 */
/******************************************************************************
 * <Includes>
 *****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <xpdbg.h>
#include <xputil.h>
#include <xpfsm.h>
#include <xphtml_fsm.h>
#include <xphtml.h>
/******************************************************************************
 * <Defines>
 *****************************************************************************/

/******************************************************************************
 * <Typedefs>
 *****************************************************************************/

/******************************************************************************
 * <Local variables>
 *****************************************************************************/

/******************************************************************************
 * <Local declarations>
 *****************************************************************************/
xphtml_node* xphtml_node_create(xphtml_node_type type, char *name);
char* xphtml_node_getname(xphtml_node *node);
xphtml_node_type xphtml_node_gettype(xphtml_node *node);
xphtml_node* xphtml_node_getlast(xphtml_node *node);
xphtml_element* xphtml_element_create(char *name);
void xphtml_element_delete(xphtml_element *element);
void xphtml_element_add_child(xphtml_element *element, xphtml_node *child);

xphtml_attribute* xphtml_attr_create(char *name);
xphtml_attribute* xphtml_attr_createx(char *name, char* value);
void xphtml_attr_setvalue(xphtml_attribute *attr, char *value);
void xphtml_attr_delete(xphtml_attribute *attr);

xphtml_context* xphtml_context_create();
void xphtml_context_delete(xphtml_context *ctx);
void xphtml_context_setchar(xphtml_context *ctx, unsigned char ch);
void xphtml_context_pushchar(xphtml_context *ctx);
unsigned char xphtml_context_popchar(xphtml_context *ctx);
char* xphtml_context_getchars(xphtml_context *ctx);
void xphtml_context_clearchars(xphtml_context *ctx);
void xphtml_context_on_opentag(xphtml_context *ctx);
void xphtml_context_on_closetag(xphtml_context *ctx);
void xphtml_context_on_attrname(xphtml_context *ctx);
void xphtml_context_on_attrvalue(xphtml_context *ctx);
void xphtml_context_on_text(xphtml_context *ctx);
void xphtml_context_on_tagname(xphtml_context *ctx);
void xphtml_context_on_tagclose(xphtml_context *ctx);
void xphtml_contex_on_tagend(xphtml_context *ctx);

/******************************************************************************
 * <Implementation>
 *****************************************************************************/
xphtml_node* xphtml_node_create(xphtml_node_type type, char *name)
{
    xphtml_node *node = (xphtml_node *)malloc(sizeof(xphtml_node));
    memset((void *)node, 0, sizeof(xphtml_node));
    node->type = type;
    node->name = name;

    return node;
}

void xphtml_node_delete(xphtml_node *node)
{
    free(node->name);
    free(node);
}

char* xphtml_node_getname(xphtml_node *node)
{
    return node->name;
}

xphtml_node_type xphtml_node_gettype(xphtml_node *node)
{
    return node->type;
}

xphtml_node* xphtml_node_getlast(xphtml_node *node)
{
    xphtml_node *nodeRef = node;
    xphtml_node *nodePrev = NULL;

    while (nodeRef != NULL)
    {
        nodePrev = nodeRef;
        nodeRef = nodeRef->next;
    }
    return nodePrev;
}

xphtml_element* xphtml_element_create(char *name)
{
    xphtml_element *element = (xphtml_element *)malloc(sizeof(xphtml_element));
    memset((void *)element, 0, sizeof(xphtml_element));

    ((xphtml_node *)element)->name = name;
    ((xphtml_node *)element)->type = XPHTML_NODE_ELEMENT;

    return element;
}

void xphtml_element_delete(xphtml_element *element)
{
    xphtml_node *nodeRef = element->childList;
    xphtml_node *nodeNext;

    while (nodeRef != NULL)
    {
        nodeNext = nodeRef->next;
        switch (nodeRef->type)
        {
        case XPHTML_NODE_ATTRIBUTE:
            xphtml_attr_delete((xphtml_attribute *)nodeRef);
            break;
        case XPHTML_NODE_ELEMENT:
            xphtml_element_delete((xphtml_element *)nodeRef);
            break;
        default:
            xphtml_node_delete(nodeRef);
            break;
        }
        nodeRef = nodeNext;
    }
    free(((xphtml_node *)element)->name);
    free(element);
}

void xphtml_element_add_child(xphtml_element *element, xphtml_node *child)
{
    xphtml_node *nodeRef = element->childList;
    xphtml_node *nodePrev = NULL;

    while (nodeRef != NULL)
    {
        nodePrev = nodeRef;
        nodeRef = nodeRef->next;
    }
    if (nodePrev == NULL)
        element->childList = child;
    else
        nodePrev->next = child;
}

void xphtml_node_show(xphtml_node *node, int level)
{
    if (node == NULL)
        return;
    xphtml_node_type type = node->type;
    xphtml_node *nodeRef;

    int i;
    printf("\n");
    for(i = 0; i < level; i ++)
        printf("   ");
    switch (type)
    {
    case XPHTML_NODE_ELEMENT:
        printf("[Element]: %s ", node->name);
        nodeRef = ((xphtml_element *)node)->childList;
        if (nodeRef != NULL)
            xphtml_node_show(nodeRef, level + 1);
        break;
    case XPHTML_NODE_ATTRIBUTE:
        printf("[Attribute]: %s=%s", node->name, ((xphtml_attribute *)node)->value);
        break;
    case XPHTML_NODE_TEXT:
        printf("[Text]: %s", node->name);
        break;
    }
    if (node->next != NULL)
        xphtml_node_show(node->next, level);

}

xphtml_attribute* xphtml_attr_create(char *name)
{
    xphtml_attribute *attr = (xphtml_attribute *)malloc(sizeof(xphtml_attribute));
    memset((void *)attr, 0, sizeof(xphtml_attribute));

    ((xphtml_node *)attr)->type = XPHTML_NODE_ATTRIBUTE;
    ((xphtml_node *)attr)->name = name;

    return attr;
}

xphtml_attribute* xphtml_attr_createx(char *name, char* value)
{
    xphtml_attribute *attr = (xphtml_attribute *)malloc(sizeof(xphtml_attribute));
    memset((void *)attr, 0, sizeof(xphtml_attribute));

    ((xphtml_node *)attr)->type = XPHTML_NODE_ATTRIBUTE;
    ((xphtml_node *)attr)->name = name;
    attr->value = value;

    return attr;
}

void xphtml_attr_setvalue(xphtml_attribute *attr, char *value)
{
    if (attr->value != NULL)
        free(attr->value);
    attr->value = value;
}

void xphtml_attr_delete(xphtml_attribute *attr)
{
    free(((xphtml_node *)attr)->name);
    free(attr->value);
    free(attr);
}

xphtml_context* xphtml_context_create()
{
    xphtml_context *ctx = (xphtml_context *)malloc(sizeof(xphtml_context));
    memset((void *)ctx, 0, sizeof(xphtml_context));

    ctx->charStack = xpcharstack_create();
    ctx->nodeStack = xpptrstack_create();

    return ctx;
}

void xphtml_context_delete(xphtml_context *ctx)
{
    xpcharstack_delete(ctx->charStack);
    xpptrstack_delete(ctx->nodeStack);
    free(ctx);
}

void xphtml_context_setchar(xphtml_context *ctx, unsigned char ch)
{
    ctx->eventChar = ch;
}

void xphtml_context_pushchar(xphtml_context *ctx)
{
    xpcharstack_push(ctx->charStack, ctx->eventChar);
}

unsigned char xphtml_context_popchar(xphtml_context *ctx)
{
    return xpcharstack_pop(ctx->charStack);
}

char* xphtml_context_getchars(xphtml_context *ctx)
{
    char *charsInStack = (char*)xpcharstack_getchars(ctx->charStack);
    unsigned int size = xpcharstack_getsize(ctx->charStack);
    if (size == 0)
        return NULL;
    char *chars = (char *)malloc(size + 1);
    memset((void *)chars, 0, size + 1);
    memcpy((void *)chars, (void *)charsInStack, size);
    xpcharstack_clear(ctx->charStack);
    return chars;
}

void xphtml_context_clearchars(xphtml_context *ctx)
{
    xpcharstack_clear(ctx->charStack);
}

void xphtml_context_on_opentag(xphtml_context *ctx)
{
    char* name = ctx->tagName;

    xphtml_element *element = xphtml_element_create(name);
    if (ctx->curElement != NULL)
        xphtml_element_add_child(ctx->curElement, (xphtml_node *)element);
    else
    {
        xphtml_node *nodeRef = xphtml_node_getlast((xphtml_node *)ctx->rootNode);
        if (nodeRef != NULL)
            nodeRef->next = (xphtml_node *)element;
    }
    if (ctx->rootNode == NULL)
        ctx->rootNode = (xphtml_node *)element;
    ctx->curElement = element;
    xpptrstack_push(ctx->nodeStack, (void *)element);
}

void xphtml_context_on_closetag(xphtml_context *ctx)
{
    char* nodeName;
    char* name = ctx->tagName;

    xphtml_node *node = (xphtml_node *)xpptrstack_pop(ctx->nodeStack);
    while (node != NULL)
    {
        XPASSERT(xphtml_node_gettype(node) == XPHTML_NODE_ELEMENT)
        nodeName = xphtml_node_getname(node);
        if (strcmp(name, nodeName) == 0)
            break;
        node = (xphtml_node *)xpptrstack_pop(ctx->nodeStack);
    }
    node = (xphtml_node *)xpptrstack_gettop(ctx->nodeStack);
    if (node != NULL)
    {
        XPASSERT(xphtml_node_gettype(node) == XPHTML_NODE_ELEMENT)
    }
    ctx->curElement = (xphtml_element *)node;
}

void xphtml_context_on_attrname(xphtml_context *ctx)
{
    if (ctx->isDeclare)
        return;
    char* name = xphtml_context_getchars(ctx);

    xphtml_attribute *attr = xphtml_attr_create(name);
    XPASSERT(ctx->curElement != NULL)
    xphtml_element_add_child(ctx->curElement, (xphtml_node *)attr);
    xpptrstack_push(ctx->nodeStack, (void *)attr);
}

void xphtml_context_on_attrvalue(xphtml_context *ctx)
{
    if (ctx->isDeclare)
        return;
    char* value = xphtml_context_getchars(ctx);

    xphtml_node *node = (xphtml_node *)xpptrstack_pop(ctx->nodeStack);
    XPASSERT(xphtml_node_gettype(node) == XPHTML_NODE_ATTRIBUTE)
    xphtml_attr_setvalue((xphtml_attribute *)node, value);
}

void xphtml_context_on_text(xphtml_context *ctx)
{
    char* text = xphtml_context_getchars(ctx);

    if (text == NULL)
        return;
    xphtml_node* node = xphtml_node_create(XPHTML_NODE_TEXT, text);
    if (ctx->curElement != NULL)
        xphtml_element_add_child(ctx->curElement, node);
    else
    {
        xphtml_node *nodeRef = xphtml_node_getlast(ctx->rootNode);
        if (nodeRef != NULL)
            nodeRef->next = node;
        else
            ctx->rootNode = node;
    }
}

void xphtml_context_on_tagname(xphtml_context *ctx)
{
    if (ctx->isDeclare)
        return;

    XPASSERT(ctx->tagName == NULL)
    ctx->tagName = xphtml_context_getchars(ctx);
    if (ctx->isTagClose)
    {
        xphtml_context_on_closetag(ctx);
        free(ctx->tagName);
        ctx->tagName = NULL;
    }
    else
        xphtml_context_on_opentag(ctx);
}

void xphtml_context_on_tagclose(xphtml_context *ctx)
{
    if (ctx->isDeclare)
        return;

    XPASSERT(ctx->isTagClose == 0)
    ctx->isTagClose = 1;
    if (ctx->tagName != NULL)
        xphtml_context_on_closetag(ctx);
}

void xphtml_context_on_tagend(xphtml_context *ctx)
{
    ctx->tagName = NULL;
    ctx->isTagClose = 0;
    ctx->isDeclare = 0;
}

void xphtml_context_on_declaration(xphtml_context *ctx)
{
    ctx->isDeclare = 1;
}

void html_tag_close_exit(void* ctx)
{
    xphtml_context_on_tagclose((xphtml_context *)ctx);
}

void html_tag_name_exit(void* ctx)
{
    xphtml_context_on_tagname((xphtml_context *)ctx);
}

void html_tag_end_exit(void* ctx)
{
    xphtml_context_on_tagend((xphtml_context *)ctx);
}

void html_text_exit(void *ctx)
{
    xphtml_context_on_text((xphtml_context *)ctx);
}

void html_attr_name_exit(void *ctx)
{
    xphtml_context_on_attrname((xphtml_context *)ctx);
}

void html_attr_value_end_enter(void *ctx)
{
    xphtml_context_on_attrvalue((xphtml_context *)ctx);
}

void html_declaration_exit(void *ctx)
{
    xphtml_context_on_declaration((xphtml_context *)ctx);
}

void html_clear_chars(void *ctx)
{
    xphtml_context_clearchars((xphtml_context *)ctx);
}

void html_state_transition(void *ctx)
{
    xphtml_context_pushchar((xphtml_context *)ctx);
}

xphtml_node* xphtml_parsefile(char *filename)
{
    xphtml_context *htmlCtx = xphtml_context_create();
    xpfsm_context *fsm = xpfsm_createx(html_states, HTML_STATE_IDLE);

    xpfsm_set_state_exit(fsm, HTML_STATE_TAG_CLOSE, html_tag_close_exit, htmlCtx);
    xpfsm_set_state_exit(fsm, HTML_STATE_TAG_NAME, html_tag_name_exit, htmlCtx);
    xpfsm_set_state_exit(fsm, HTML_STATE_TEXT, html_text_exit, htmlCtx);
    xpfsm_set_state_exit(fsm, HTML_STATE_TAG_END, html_tag_end_exit, htmlCtx);
    xpfsm_set_state_exit(fsm, HTML_STATE_ATTR_NAME, html_attr_name_exit, htmlCtx);
    xpfsm_set_state_exit(fsm, HTML_STATE_DECLARATION, html_declaration_exit, htmlCtx);

    xpfsm_set_state_enter(fsm, HTML_STATE_ATTR_VALUE_END, html_attr_value_end_enter, htmlCtx);
    xpfsm_set_state_enter(fsm, HTML_STATE_TAG_NAME, html_clear_chars, htmlCtx);
    xpfsm_set_state_enter(fsm, HTML_STATE_ATTR_NAME, html_clear_chars, htmlCtx);
    xpfsm_set_state_exit(fsm, HTML_STATE_ATTR_VALUE_START, html_clear_chars, htmlCtx);
    xpfsm_set_state_enter(fsm, HTML_STATE_TEXT, html_clear_chars, htmlCtx);

    FILE *fp = fopen(filename, "r");
    if (fp == NULL)
    {
        printf("Open %s failed!\n", filename);
        return NULL;
    }
    unsigned char e;
    int c;
    while ( (c = fgetc(fp)) != EOF)
    {
        e = (unsigned char)c;
        xphtml_context_setchar(htmlCtx, e);
        xpfsm_process_event(fsm, e);
        xphtml_context_pushchar(htmlCtx);
    }
    if (xpfsm_get_state(fsm) == HTML_STATE_TEXT)
        xphtml_context_on_text(htmlCtx);
    fclose(fp);
    xphtml_node *node = htmlCtx->rootNode;
    xphtml_context_delete(htmlCtx);
    xpfsm_delete(fsm);

    return node;
}

xphtml_node* xphtml_parsestring(char* str)
{
    xphtml_context *htmlCtx = xphtml_context_create();
    xpfsm_context *fsm = xpfsm_createx(html_states, HTML_STATE_IDLE);

    xpfsm_set_state_exit(fsm, HTML_STATE_TAG_CLOSE, html_tag_close_exit, htmlCtx);
    xpfsm_set_state_exit(fsm, HTML_STATE_TAG_NAME, html_tag_name_exit, htmlCtx);
    xpfsm_set_state_exit(fsm, HTML_STATE_TEXT, html_text_exit, htmlCtx);
    xpfsm_set_state_exit(fsm, HTML_STATE_TAG_END, html_tag_end_exit, htmlCtx);
    xpfsm_set_state_exit(fsm, HTML_STATE_ATTR_NAME, html_attr_name_exit, htmlCtx);
    xpfsm_set_state_exit(fsm, HTML_STATE_DECLARATION, html_declaration_exit, htmlCtx);

    xpfsm_set_state_enter(fsm, HTML_STATE_ATTR_VALUE_END, html_attr_value_end_enter, htmlCtx);
    xpfsm_set_state_enter(fsm, HTML_STATE_TAG_NAME, html_clear_chars, htmlCtx);
    xpfsm_set_state_enter(fsm, HTML_STATE_ATTR_NAME, html_clear_chars, htmlCtx);
    xpfsm_set_state_exit(fsm, HTML_STATE_ATTR_VALUE_START, html_clear_chars, htmlCtx);
    xpfsm_set_state_enter(fsm, HTML_STATE_TEXT, html_clear_chars, htmlCtx);
    unsigned char *ref = (unsigned char *)str;
    while ( *ref != '\0')
    {
        xphtml_context_setchar(htmlCtx, *ref);
        xpfsm_process_event(fsm, *ref);
        xphtml_context_pushchar(htmlCtx);
        ref ++;
    }
    if (xpfsm_get_state(fsm) == HTML_STATE_TEXT)
        xphtml_context_on_text(htmlCtx);
    xphtml_node *node = htmlCtx->rootNode;
    xphtml_context_delete(htmlCtx);
    xpfsm_delete(fsm);

    return node;
}


